package com.gitee.wsl.doc.sample.opml

import com.gitee.wsl.collections.stack.stackOf
import com.gitee.wsl.doc.sample.opml.ValidityCheck.isTextBlank
import com.gitee.wsl.doc.sample.opml.ValidityCheck.requireNoText
import com.gitee.wsl.doc.sample.opml.ValidityCheck.requirePosition
import com.gitee.wsl.doc.sample.opml.bean.Body
import com.gitee.wsl.doc.sample.opml.bean.Outline
import com.gitee.wsl.doc.xml.EventType
import com.gitee.wsl.doc.xml.XmlPullParser
import com.gitee.wsl.text.format.format

/**
 * [com.gitee.wsl.doc.sample.opml.OpmlSectionHandler] that deals with the `body` tag
 */
internal class OpmlBodyHandler :
    com.gitee.wsl.doc.sample.opml.OpmlSectionHandler<com.gitee.wsl.doc.sample.opml.bean.Body?> {
    private val outlineBuilderStack = stackOf<OutlineBuilder>()
    private val elementStack = stackOf<String>()

    private val outlineBuilders: MutableList<OutlineBuilder> = ArrayList()

    @Throws(_root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class)
    override fun startTag(xpp: XmlPullParser) {
        requirePosition(xpp, EventType.START_TAG)

        when (xpp.name) {
            "body" -> {}
            "outline" -> {
                val outlineBuilder = parseOutlineBuilder(xpp)
                if (outlineBuilderStack.isEmpty()) {
                    // this outline is a child of <body>
                    outlineBuilders.add(outlineBuilder)
                } else {
                    // this outline is nested in a different <outline>
                    outlineBuilderStack.peek().subElements.add(outlineBuilder)
                }
                outlineBuilderStack.push(outlineBuilder)
            }

            else ->            // An OPML file may contain elements and attributes not described on this page,
                // only if those elements are defined in a namespace, as specified by the W3C.
                // http://opml.org/spec2.opml#1629042982000
                if (isTextBlank(xpp.namespace)) {
                    throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(
                        String.format(
                            "encountered non-namespaced element <%s> instead of <outline>",
                            xpp.name
                        )
                    )
                }
        }

        elementStack.push(xpp.name)
    }

    @Throws(_root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class)
    override fun text(xpp: XmlPullParser) {
        when (elementStack.first()) {
            "outline", "body" -> requireNoText(xpp, elementStack.first(), true)
            else -> {}
        }
    }

    @Throws(_root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class)
    override fun endTag(xpp: XmlPullParser) {
        requirePosition(xpp, EventType.END_TAG)

        val closed = elementStack.pop()

        if (xpp.name != closed) {
            throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(
                String.format(
                    "required element <%s> but found <%s>",
                    closed,
                    xpp.name
                )
            )
        }

        when (xpp.name) {
            "outline" -> {
                if (outlineBuilderStack.isEmpty()) {
                    throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("invalid nesting of outline elements")
                }
                outlineBuilderStack.pop()
            }

            "body" -> if (!outlineBuilderStack.isEmpty()) {
                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException("unclosed outline element(s)")
            }

            else -> if (isTextBlank(xpp.namespace)) {
                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(
                    String.format(
                        "encountered non-namespaced element <%s> instead of <outline> or <body>",
                        xpp.name
                    )
                )
            }
        }
    }

    override fun get(): com.gitee.wsl.doc.sample.opml.bean.Body {
        val outlines: MutableList<com.gitee.wsl.doc.sample.opml.bean.Outline> = ArrayList()
        for (subElement in outlineBuilders) {
            outlines.add(build(subElement))
        }
        return _root_ide_package_.com.gitee.wsl.doc.sample.opml.bean.Body(outlines)
    }

    private fun build(builder: OutlineBuilder): com.gitee.wsl.doc.sample.opml.bean.Outline {
        val subElements: MutableList<com.gitee.wsl.doc.sample.opml.bean.Outline> = ArrayList()
        for (subElement in builder.subElements) {
            subElements.add(build(subElement))
        }
        return _root_ide_package_.com.gitee.wsl.doc.sample.opml.bean.Outline(builder.attributes, subElements)
    }

    @Throws(_root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException::class)
    private fun parseOutlineBuilder(xpp: XmlPullParser): OutlineBuilder {
        val outlineBuilder = OutlineBuilder()
        for (i in 0 until xpp.attributeCount) {
            val name = xpp.getAttributeName(i)
            if (outlineBuilder.attributes.containsKey(name)) {
                throw _root_ide_package_.com.gitee.wsl.doc.sample.opml.OpmlParseException(
                    String.format(
                        "element %s contains attribute %s more than once",
                        xpp.name,
                        name
                    )
                )
            }
            outlineBuilder.attributes[xpp.getAttributeName(i)] = xpp.getAttributeValue(i)
        }
        return outlineBuilder
    }

    private class OutlineBuilder {
        val attributes: MutableMap<String, String> = HashMap()
        val subElements: MutableList<OutlineBuilder> = ArrayList()
    }
}
